package drds.common.db.xml_mapping.executor;


import drds.common.db.xml_mapping.mapping.BindSql;
import drds.common.db.xml_mapping.mapping.Mapper;
import drds.common.db.xml_mapping.reflection.MetaObject;
import drds.common.db.xml_mapping.reflection.factory.ObjectFactory;
import drds.common.db.xml_mapping.session.Configuration;
import drds.common.db.xml_mapping.session.ResultHandler;
import drds.common.db.xml_mapping.session.RowBounds;
import drds.common.db.xml_mapping.transaction.Transaction;
import lombok.extern.slf4j.Slf4j;

import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.List;
import java.util.concurrent.ConcurrentLinkedQueue;


@Slf4j
public abstract class BaseExecutor implements Executor {


  protected Transaction transaction;
  protected Executor executor;

  protected ConcurrentLinkedQueue<DeferredLoad> deferredLoads;

  protected Configuration configuration;

  protected int queryStack;
  private boolean closed;

  protected BaseExecutor(Configuration configuration, Transaction transaction) {
    this.transaction = transaction;
    this.deferredLoads = new ConcurrentLinkedQueue<>();

    this.closed = false;
    this.configuration = configuration;
    this.executor = this;
  }

  @Override
  public Transaction getTransaction() {
    if (closed) {
      throw new ExecutorException("Executor was closed.");
    }
    return transaction;
  }

  @Override
  public void close(boolean forceRollback) {
    try {
      try {
        rollback(forceRollback);
      } finally {
        if (transaction != null) {
          transaction.close();
        }
      }
    } catch (SQLException e) {
      // Ignore.  There's nothing that can be done at this point.
      log.warn("Unexpected exception on closing transaction.  Cause: " + e);
    } finally {
      transaction = null;
      deferredLoads = null;

      closed = true;
    }
  }

  @Override
  public boolean isClosed() {
    return closed;
  }

  @Override
  public int update(Mapper mapper, Object parameter) throws SQLException {
    if (closed) {
      throw new ExecutorException("Executor was closed.");
    }

    return doUpdate(mapper, parameter);
  }

  @Override
  public List<BatchResult> flushStatements() throws SQLException {
    return flushStatements(false);
  }

  public List<BatchResult> flushStatements(boolean isRollBack) throws SQLException {
    if (closed) {
      throw new ExecutorException("Executor was closed.");
    }
    return doFlushStatements(isRollBack);
  }

  @Override
  public <E> List<E> query(Mapper mapper, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
    BindSql bindSql = mapper.getBoundSql(parameter);

    return query(mapper, parameter, rowBounds, resultHandler, bindSql);
  }

  @SuppressWarnings("unchecked")
  @Override
  public <E> List<E> query(Mapper mapper, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BindSql bindSql) throws SQLException {
    if (closed) {
      throw new ExecutorException("Executor was closed.");
    }

    List<E> list = null;
    try {
      queryStack++;
      {
        list = queryFromDatabase(mapper, parameter, rowBounds, resultHandler, bindSql);
      }
    } finally {
      queryStack--;
    }
    if (queryStack == 0) {
      for (DeferredLoad deferredLoad : deferredLoads) {
        deferredLoad.load();
      }
      deferredLoads.clear();
    }
    return list;
  }


  @Override
  public void commit(boolean required) throws SQLException {
    if (closed) {
      throw new ExecutorException("Cannot commit, transaction is already closed");
    }

    flushStatements();
    if (required) {
      transaction.commit();
    }
  }

  @Override
  public void rollback(boolean required) throws SQLException {
    if (!closed) {
      try {

        flushStatements(true);
      } finally {
        if (required) {
          transaction.rollback();
        }
      }
    }
  }


  protected abstract int doUpdate(Mapper mapper, Object parameter)
    throws SQLException;

  protected abstract List<BatchResult> doFlushStatements(boolean isRollback)
    throws SQLException;

  protected abstract <E> List<E> doQuery(Mapper mapper, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BindSql bindSql)
    throws SQLException;


  protected void closeStatement(Statement statement) {
    if (statement != null) {
      try {
        statement.close();
      } catch (SQLException e) {
        // ignore
      }
    }
  }


  private <E> List<E> queryFromDatabase(Mapper mapper, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BindSql bindSql) throws SQLException {
    List<E> list = doQuery(mapper, parameter, rowBounds, resultHandler, bindSql);
    return list;
  }

  protected Connection getConnection() throws SQLException {
    Connection connection = transaction.getConnection();
    return connection;
  }


  public void setExecutorWrapper(Executor wrapper) {
    this.executor = wrapper;
  }

  private static class DeferredLoad {

    private final MetaObject resultObject;
    private final String property;
    private final Class<?> targetType;

    private final ObjectFactory objectFactory;
    private final ResultExtractor resultExtractor;


    public DeferredLoad(MetaObject resultObject,
                        String property,

                        Configuration configuration,
                        Class<?> targetType) {
      this.resultObject = resultObject;
      this.property = property;

      this.objectFactory = configuration.getObjectFactory();
      this.resultExtractor = new ResultExtractor(configuration, objectFactory);
      this.targetType = targetType;
    }


    public void load() {

      List<Object> list = null;
      Object value = resultExtractor.extractObjectFromList(list, targetType);
      resultObject.setValue(property, value);
    }

  }

}
